<?php
/*

File: king-include/king-app/users.php
Description: User management (application level) for basic user operations

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

More about this license: LICENCE.html
 */

if ( ! defined( 'QA_VERSION' ) ) {
	// don't allow this page to be requested directly from browser
	header( 'Location: ../' );
	exit;
}

define( 'QA_USER_LEVEL_BASIC', 0 );
define( 'QA_USER_LEVEL_APPROVED', 10 );
define( 'QA_USER_LEVEL_EXPERT', 20 );
define( 'QA_USER_LEVEL_EDITOR', 50 );
define( 'QA_USER_LEVEL_MODERATOR', 80 );
define( 'QA_USER_LEVEL_ADMIN', 100 );
define( 'QA_USER_LEVEL_SUPER', 120 );

define( 'QA_USER_FLAGS_EMAIL_CONFIRMED', 1 );
define( 'QA_USER_FLAGS_USER_BLOCKED', 2 );
define( 'QA_USER_FLAGS_SHOW_AVATAR', 4 );
define( 'QA_USER_FLAGS_SHOW_GRAVATAR', 8 );
define( 'QA_USER_FLAGS_NO_MESSAGES', 16 );
define( 'QA_USER_FLAGS_NO_MAILINGS', 32 );
define( 'QA_USER_FLAGS_WELCOME_NOTICE', 64 );
define( 'QA_USER_FLAGS_MUST_CONFIRM', 128 );
define( 'QA_USER_FLAGS_NO_WALL_POSTS', 256 );
define( 'QA_USER_FLAGS_MUST_APPROVE', 512 );

define( 'QA_FIELD_FLAGS_MULTI_LINE', 1 );
define( 'QA_FIELD_FLAGS_LINK_URL', 2 );
define( 'QA_FIELD_FLAGS_ON_REGISTER', 4 );

@define( 'QA_FORM_EXPIRY_SECS', 86400 ); // how many seconds a form is valid for submission
@define( 'QA_FORM_KEY_LENGTH', 32 );

if ( QA_FINAL_EXTERNAL_USERS ) {
	//	If we're using single sign-on integration (WordPress or otherwise), load PHP file for that

	if ( defined( 'QA_FINAL_WORDPRESS_INTEGRATE_PATH' ) ) {
		require_once QA_INCLUDE_DIR . 'king-util/external-users-wp.php';
	} else {
		require_once QA_EXTERNAL_DIR . 'king-external-users.php';
	}

	//	Access functions for user information

	function qa_get_logged_in_user_cache()
	/*
	Return array of information about the currently logged in user, cache to ensure only one call to external code
	 */
	{
		global $qa_cached_logged_in_user;

		if ( ! isset( $qa_cached_logged_in_user ) ) {
			$user                     = qa_get_logged_in_user();
			$qa_cached_logged_in_user = isset( $user ) ? $user : false; // to save trying again
		}

		return @$qa_cached_logged_in_user;
	}

	/**
	 * @param $field
	 */
	function qa_get_logged_in_user_field( $field )
	/*
	Return $field of the currently logged in user, or null if not available
	 */
	{
		$user = qa_get_logged_in_user_cache();

		return @$user[$field];
	}

	function qa_get_logged_in_userid()
	/*
	Return the userid of the currently logged in user, or null if none
	 */
	{
		return qa_get_logged_in_user_field( 'userid' );
	}

	/**
	 * @return mixed
	 */
	function qa_get_logged_in_points()
	/*
	Return the number of points of the currently logged in user, or null if none is logged in
	 */
	{
		global $qa_cached_logged_in_points;

		if ( ! isset( $qa_cached_logged_in_points ) ) {
			require_once QA_INCLUDE_DIR . 'king-db/selects.php';

			$qa_cached_logged_in_points = qa_db_select_with_pending( qa_db_user_points_selectspec( qa_get_logged_in_userid(), true ) );
		}

		return $qa_cached_logged_in_points['points'];
	}

	/**
	 * @param $userid
	 * @param $size
	 * @param $padding
	 * @return null
	 */
	function qa_get_external_avatar_html( $userid, $size, $padding = false )
	/*
	Return HTML to display for the avatar of $userid, constrained to $size pixels, with optional $padding to that size
	 */
	{
		if ( function_exists( 'qa_avatar_html_from_userid' ) ) {
			return qa_avatar_html_from_userid( $userid, $size, $padding );
		} else {
			return;
		}
	}
} else {
	function qa_start_session()
	/*
	Open a PHP session if one isn't opened already
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		@ini_set( 'session.gc_maxlifetime', 86400 ); // worth a try, but won't help in shared hosting environment
		@ini_set( 'session.use_trans_sid', false ); // sessions need cookies to work, since we redirect after login
		@ini_set( 'session.cookie_domain', QA_COOKIE_DOMAIN );

		if ( ! isset( $_SESSION ) ) {
			session_start();
		}
	}

	function qa_session_var_suffix()
	/*
	Returns a suffix to be used for names of session variables to prevent them being shared between multiple KINGMEDIA sites on the same server
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		$prefix = defined( 'QA_MYSQL_USERS_PREFIX' ) ? QA_MYSQL_USERS_PREFIX : QA_MYSQL_TABLE_PREFIX;

		return md5( QA_FINAL_MYSQL_HOSTNAME . '/' . QA_FINAL_MYSQL_USERNAME . '/' . QA_FINAL_MYSQL_PASSWORD . '/' . QA_FINAL_MYSQL_DATABASE . '/' . $prefix );
	}

	/**
	 * @param $userid
	 */
	function qa_session_verify_code( $userid )
	/*
	Returns a verification code used to ensure that a user session can't be generated by another PHP script running on the same server
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		return sha1( $userid . '/' . QA_MYSQL_TABLE_PREFIX . '/' . QA_FINAL_MYSQL_DATABASE . '/' . QA_FINAL_MYSQL_PASSWORD . '/' . QA_FINAL_MYSQL_USERNAME . '/' . QA_FINAL_MYSQL_HOSTNAME );
	}

	/**
	 * @param $handle
	 * @param $sessioncode
	 * @param $remember
	 */
	function qa_set_session_cookie( $handle, $sessioncode, $remember )
	/*
	Set cookie in browser for username $handle with $sessioncode (in database).
	Pass true if user checked 'Remember me' (either now or previously, as learned from cookie).
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		// if $remember is true, store in browser for a month, otherwise store only until browser is closed
		setcookie( 'qa_session', $handle . '/' . $sessioncode . '/' . ( $remember ? 1 : 0 ), $remember ? ( time() + 2592000 ) : 0, '/', QA_COOKIE_DOMAIN );
	}

	function qa_clear_session_cookie()
	/*
	Remove session cookie from browser
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		setcookie( 'qa_session', false, 0, '/', QA_COOKIE_DOMAIN );
	}

	/**
	 * @param $userid
	 * @param $source
	 */
	function qa_set_session_user( $userid, $source )
	/*
	Set the session variables to indicate that $userid is logged in from $source
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		$suffix = qa_session_var_suffix();

		$_SESSION['qa_session_userid_' . $suffix] = $userid;
		$_SESSION['qa_session_source_' . $suffix] = $source;
		$_SESSION['qa_session_verify_' . $suffix] = qa_session_verify_code( $userid );
		// prevents one account on a shared server being able to create a log in a user to KINGMEDIA on another account on same server
	}

	function qa_clear_session_user()
	/*
	Clear the session variables indicating that a user is logged in
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		$suffix = qa_session_var_suffix();

		unset( $_SESSION['qa_session_userid_' . $suffix] );
		unset( $_SESSION['qa_session_source_' . $suffix] );
		unset( $_SESSION['qa_session_verify_' . $suffix] );
	}

	/**
	 * @param $userid
	 * @param $handle
	 * @param $remember
	 * @param false $source
	 */
	function qa_set_logged_in_user( $userid, $handle = '', $remember = false, $source = null )
	/*
	Call for successful log in by $userid and $handle or successful log out with $userid=null.
	$remember states if 'Remember me' was checked in the login form.
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		require_once QA_INCLUDE_DIR . 'king-app/cookies.php';

		qa_start_session();

		if ( isset( $userid ) ) {
			qa_set_session_user( $userid, $source );

			// PHP sessions time out too quickly on the server side, so we also set a cookie as backup.
			// Logging in from a second browser will make the previous browser's 'Remember me' no longer
			// work - I'm not sure if this is the right behavior - could see it either way.

			require_once QA_INCLUDE_DIR . 'king-db/selects.php';

			$userinfo = qa_db_single_select( qa_db_user_account_selectspec( $userid, true ) );

			// if we have logged in before, and are logging in the same way as before, we don't need to change the sessioncode/source
			// this means it will be possible to automatically log in (via cookies) to the same account from more than one browser

			if ( empty( $userinfo['sessioncode'] ) || ( $source !== $userinfo['sessionsource'] ) ) {
				$sessioncode = qa_db_user_rand_sessioncode();
				qa_db_user_set( $userid, 'sessioncode', $sessioncode );
				qa_db_user_set( $userid, 'sessionsource', $source );
			} else {
				$sessioncode = $userinfo['sessioncode'];
			}

			qa_db_user_logged_in( $userid, qa_remote_ip_address() );
			qa_set_session_cookie( $handle, $sessioncode, $remember );

			qa_report_event( 'u_login', $userid, $userinfo['handle'], qa_cookie_get() );
		} else {
			$olduserid = qa_get_logged_in_userid();
			$oldhandle = qa_get_logged_in_handle();

			qa_clear_session_cookie();
			qa_clear_session_user();

			qa_report_event( 'u_logout', $olduserid, $oldhandle, qa_cookie_get() );
		}
	}

	/**
	 * @param $source
	 * @param $identifier
	 * @param $fields
	 */
	function qa_log_in_external_user( $source, $identifier, $fields )
	/*
	Call to log in a user based on an external identity provider $source with external $identifier
	A new user is created based on $fields if it's a new combination of $source and $identifier
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		require_once QA_INCLUDE_DIR . 'king-db/users.php';

		$users      = qa_db_user_login_find( $source, $identifier );
		$countusers = count( $users );

		if ( $countusers > 1 ) {
			qa_fatal_error( 'External login mapped to more than one user' );
		}
		// should never happen

		if ( $countusers ) // user exists so log them in
		{
			qa_set_logged_in_user( $users[0]['userid'], $users[0]['handle'], false, $source );
		} else {
			// create and log in user
			require_once QA_INCLUDE_DIR . 'king-app/users-edit.php';

			qa_db_user_login_sync( true );

			$users = qa_db_user_login_find( $source, $identifier ); // check again after table is locked

			if ( count( $users ) == 1 ) {
				qa_db_user_login_sync( false );
				qa_set_logged_in_user( $users[0]['userid'], $users[0]['handle'], false, $source );
			} else {
				$handle = qa_handle_make_valid( @$fields['handle'] );

				if ( strlen( $fields['email'] ?? '' ) ) {
					// remove email address if it will cause a duplicate
					$emailusers = qa_db_user_find_by_email( $fields['email'] );

					if ( count( $emailusers ) ) {
						qa_redirect( 'login', array( 'e' => $fields['email'], 'ee' => '1' ) );
						unset( $fields['email'] );
						unset( $fields['confirmed'] );
					}
				}

				$userid = qa_create_new_user( (string) @$fields['email'], null/* no password */, $handle,
					isset( $fields['level'] ) ? $fields['level'] : QA_USER_LEVEL_BASIC, @$fields['confirmed'] );

				qa_db_user_login_add( $userid, $source, $identifier );
				qa_db_user_login_sync( false );

				$profilefields = array( 'name', 'location', 'website', 'about' );

				foreach ( $profilefields as $fieldname ) {
					if ( strlen( $fields[$fieldname] ?? '' ) ) {
						qa_db_user_profile_set( $userid, $fieldname, $fields[$fieldname] );
					}
				}

				if ( strlen( $fields['avatar'] ?? '' ) ) {
					qa_set_user_avatar( $userid, $fields['avatar'] );
				}

				qa_set_logged_in_user( $userid, $handle, false, $source );
			}
		}
	}

	function qa_get_logged_in_userid()
	/*
	Return the userid of the currently logged in user, or null if none logged in
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		global $qa_logged_in_userid_checked;

		$suffix = qa_session_var_suffix();

		if ( ! $qa_logged_in_userid_checked ) {
			// only check once
			qa_start_session(); // this will load logged in userid from the native PHP session, but that's not enough

			$sessionuserid = @$_SESSION['qa_session_userid_' . $suffix];

			if ( isset( $sessionuserid ) ) // check verify code matches

			{
				if ( @$_SESSION['qa_session_verify_' . $suffix] != qa_session_verify_code( $sessionuserid ) ) {
					qa_clear_session_user();
				}
			}

			if ( ! empty( $_COOKIE['qa_session'] ) ) {
				@list( $handle, $sessioncode, $remember ) = explode( '/', $_COOKIE['qa_session'] );

				if ( $remember ) {
					qa_set_session_cookie( $handle, $sessioncode, $remember );
				}
				// extend 'remember me' cookies each time

				$sessioncode = trim( $sessioncode ); // trim to prevent passing in blank values to match uninitiated DB rows

				// Try to recover session from the database if PHP session has timed out

				if (  ( ! isset( $_SESSION['qa_session_userid_' . $suffix] ) ) && ( ! empty( $handle ) ) && ( ! empty( $sessioncode ) ) ) {
					require_once QA_INCLUDE_DIR . 'king-db/selects.php';

					$userinfo = qa_db_single_select( qa_db_user_account_selectspec( $handle, false ) ); // don't get any pending

					if ( strtolower( trim( $userinfo['sessioncode'] ) ) == strtolower( $sessioncode ) ) {
						qa_set_session_user( $userinfo['userid'], $userinfo['sessionsource'] );
					} else {
						qa_clear_session_cookie();
					}
					// if cookie not valid, remove it to save future checks
				}
			}

			$qa_logged_in_userid_checked = true;
		}

		return @$_SESSION['qa_session_userid_' . $suffix];
	}

	function qa_get_logged_in_source()
	/*
	Get the source of the currently logged in user, from call to qa_log_in_external_user() or null if logged in normally
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		$userid = qa_get_logged_in_userid();
		$suffix = qa_session_var_suffix();

		if ( isset( $userid ) ) {
			return @$_SESSION['qa_session_source_' . $suffix];
		}
	}

	/**
	 * @param $field
	 */
	function qa_get_logged_in_user_field( $field )
	/*
	Return $field of the currently logged in user, cache to ensure only one call to external code
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		global $qa_cached_logged_in_user;

		$userid = qa_get_logged_in_userid();

		if ( isset( $userid ) && ! isset( $qa_cached_logged_in_user ) ) {
			require_once QA_INCLUDE_DIR . 'king-db/selects.php';
			$qa_cached_logged_in_user = qa_db_get_pending_result( 'loggedinuser', qa_db_user_account_selectspec( $userid, true ) );

			if ( ! isset( $qa_cached_logged_in_user ) ) {
				// the user can no longer be found (should only apply to deleted users)
				qa_clear_session_user();
				qa_redirect( '' ); // implicit exit;
			}
		}

		return @$qa_cached_logged_in_user[$field];
	}

	function qa_get_logged_in_points()
	/*
	Return the number of points of the currently logged in user, or null if none is logged in
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		return qa_get_logged_in_user_field( 'points' );
	}

	function qa_get_mysql_user_column_type()
	/*
	Return column type to use for users (if not using single sign-on integration)
	 */
	{
		return 'INT UNSIGNED';
	}

	/**
	 * @param $handle
	 * @param $microformats
	 * @param false $favorited
	 */
	function qa_get_one_user_html( $handle, $microformats = false, $favorited = false )
	/*
	Return HTML to display for user with username $handle, with microformats if $microformats is true. Set $favorited to true to show the user as favorited.
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		if ( ! strlen( (string)$handle ) ) {
			return '';
		}

		$url      = qa_path_html( 'user/' . $handle );
		$favclass = $favorited ? ' king-user-favorited' : '';
		$mfclass  = $microformats ? ' url nickname' : '';

		return '<a href="' . $url . '" class="king-user-link' . $favclass . $mfclass . '">' . qa_html( $handle ) . '</a>';
	}

	/**
	 * @param $flags
	 * @param $email
	 * @param $handle
	 * @param $blobid
	 * @param $width
	 * @param $height
	 * @param $size
	 * @param $padding
	 */
	function qa_get_user_avatar_html( $flags, $email, $handle, $blobid, $width, $height, $size, $padding = false )
	/*
	Return HTML to display for the user's avatar, constrained to $size pixels, with optional $padding to that size
	Pass the user's fields $flags, $email, $handle, and avatar $blobid, $width and $height
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		require_once QA_INCLUDE_DIR . 'king-app/format.php';

		if ( qa_opt( 'avatar_allow_gravatar' ) && ( $flags & QA_USER_FLAGS_SHOW_GRAVATAR ) ) {
			$html = qa_get_gravatar_html( $email, $size );
		} elseif ( qa_opt( 'avatar_allow_upload' ) && (  ( $flags & QA_USER_FLAGS_SHOW_AVATAR ) ) && isset( $blobid ) ) {
			$html = qa_get_avatar_blob_html( $blobid, $width, $height, $size, $padding );
		} elseif (  ( qa_opt( 'avatar_allow_gravatar' ) || qa_opt( 'avatar_allow_upload' ) ) && qa_opt( 'avatar_default_show' ) && strlen( (string)qa_opt( 'avatar_default_blobid' ) ) ) {
			$html = qa_get_avatar_blob_html( qa_opt( 'avatar_default_blobid' ), qa_opt( 'avatar_default_width' ), qa_opt( 'avatar_default_height' ), $size, $padding );
		} else {
			$html = null;
		}

		return ( isset( $html ) && strlen( (string)$handle ) ) ? ( '<a href="' . qa_path_html( 'user/' . $handle ) . '" class="king-avatar-link">' . $html . '</a>' ) : $html;
	}

	/**
	 * @param $userid
	 * @return mixed
	 */
	function qa_get_user_email( $userid )
	/*
	Return email address for user $userid (if not using single sign-on integration)
	 */
	{
		$userinfo = qa_db_select_with_pending( qa_db_user_account_selectspec( $userid, true ) );

		return $userinfo['email'];
	}

	/**
	 * @param $userid
	 * @param $action
	 */
	function qa_user_report_action( $userid, $action )
	/*
	Called after a database write $action performed by a user $userid
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		require_once QA_INCLUDE_DIR . 'king-db/users.php';

		qa_db_user_written( $userid, qa_remote_ip_address() );
	}

	/**
	 * @param $level
	 */
	function qa_user_level_string( $level )
	/*
	Return textual representation of the user $level
	 */
	{
		if ( qa_to_override( __FUNCTION__ ) ) {
			$args = func_get_args();

			return qa_call_override( __FUNCTION__, $args );}

		if ( $level >= QA_USER_LEVEL_SUPER ) {
			$string = 'users/level_super';
		} elseif ( $level >= QA_USER_LEVEL_ADMIN ) {
			$string = 'users/level_admin';
		} elseif ( $level >= QA_USER_LEVEL_MODERATOR ) {
			$string = 'users/level_moderator';
		} elseif ( $level >= QA_USER_LEVEL_EDITOR ) {
			$string = 'users/level_editor';
		} elseif ( $level >= QA_USER_LEVEL_EXPERT ) {
			$string = 'users/level_expert';
		} elseif ( $level >= QA_USER_LEVEL_APPROVED ) {
			$string = 'users/approved_user';
		} else {
			$string = 'users/registered_user';
		}

		return qa_lang( $string );
	}

	/**
	 * @param $rooturl
	 * @param $tourl
	 */
	function qa_get_login_links( $rooturl, $tourl )
	/*
	Return an array of links to login, register, email confirm and logout pages (if not using single sign-on integration)
	 */
	{
		return array(
			'login'    => qa_path( 'login', isset( $tourl ) ? array( 'to' => $tourl ) : null, $rooturl ),
			'register' => qa_path( 'register', isset( $tourl ) ? array( 'to' => $tourl ) : null, $rooturl ),
			'confirm'  => qa_path( 'confirm', null, $rooturl ),
			'logout'   => qa_path( 'logout', null, $rooturl ),
		);
	}
}
// end of: if (QA_FINAL_EXTERNAL_USERS) { ... } else { ... }

function qa_is_logged_in()
/*
Return whether someone is logged in at the moment
 */
{
	$userid = qa_get_logged_in_userid();

	return isset( $userid );
}

function qa_get_logged_in_handle()
/*
Return displayable handle/username of currently logged in user, or null if none
 */
{
	return qa_get_logged_in_user_field( QA_FINAL_EXTERNAL_USERS ? 'publicusername' : 'handle' );
}

function qa_get_logged_in_email()
/*
Return email of currently logged in user, or null if none
 */
{
	return qa_get_logged_in_user_field( 'email' );
}

function qa_get_logged_in_level()
/*
Return level of currently logged in user, or null if none
 */
{
	return qa_get_logged_in_user_field( 'level' );
}

function qa_get_logged_in_flags()
/*
Return flags (see QA_USER_FLAGS_*) of currently logged in user, or null if none
 */
{
	if ( QA_FINAL_EXTERNAL_USERS ) {
		return qa_get_logged_in_user_field( 'blocked' ) ? QA_USER_FLAGS_USER_BLOCKED : 0;
	} else {
		return qa_get_logged_in_user_field( 'flags' );
	}
}

function qa_get_logged_in_levels()
/*
Return an array of all the specific (e.g. per category) level privileges for the logged in user, retrieving from the database if necessary
 */
{
	require_once QA_INCLUDE_DIR . 'king-db/selects.php';

	return qa_db_get_pending_result( 'userlevels', qa_db_user_levels_selectspec( qa_get_logged_in_userid(), true ) );
}

/**
 * @param $userids
 * @return mixed
 */
function qa_userids_to_handles( $userids )
/*
Return an array mapping each userid in $userids to that user's handle (public username), or to null if not found
 */
{
	if ( QA_FINAL_EXTERNAL_USERS ) {
		$rawuseridhandles = qa_get_public_from_userids( $userids );
	} else {
		require_once QA_INCLUDE_DIR . 'king-db/users.php';
		$rawuseridhandles = qa_db_user_get_userid_handles( $userids );
	}

	$gotuseridhandles = array();

	foreach ( $userids as $userid ) {
		$gotuseridhandles[$userid] = @$rawuseridhandles[$userid];
	}

	return $gotuseridhandles;
}

/**
 * @param $userid
 */
function qa_userid_to_handle( $userid )
/*
Return an string mapping the received userid to that user's handle (public username), or to null if not found
 */
{
	$handles = qa_userids_to_handles( array( $userid ) );

	return empty( $handles ) ? null : $handles[$userid];
}

/**
 * @param $handles
 * @param $exactonly
 * @return mixed
 */
function qa_handles_to_userids( $handles, $exactonly = false )
/*
Return an array mapping each handle in $handles the user's userid, or null if not found. If $exactonly is true then
$handles must have the correct case and accents. Otherwise, handles are case- and accent-insensitive, and the keys
of the returned array will match the $handles provided, not necessary those in the DB.
 */
{
	require_once QA_INCLUDE_DIR . 'king-util/string.php';

	if ( QA_FINAL_EXTERNAL_USERS ) {
		$rawhandleuserids = qa_get_userids_from_public( $handles );
	} else {
		require_once QA_INCLUDE_DIR . 'king-db/users.php';
		$rawhandleuserids = qa_db_user_get_handle_userids( $handles );
	}

	$gothandleuserids = array();

	if ( $exactonly ) {
		// only take the exact matches

		foreach ( $handles as $handle ) {
			$gothandleuserids[$handle] = @$rawhandleuserids[$handle];
		}
	} else {
		// normalize to lowercase without accents, and then find matches
		$normhandleuserids = array();

		foreach ( $rawhandleuserids as $handle => $userid ) {
			$normhandleuserids[qa_string_remove_accents( qa_strtolower( $handle ) )] = $userid;
		}

		foreach ( $handles as $handle ) {
			$gothandleuserids[$handle] = @$normhandleuserids[qa_string_remove_accents( qa_strtolower( $handle ) )];
		}
	}

	return $gothandleuserids;
}

/**
 * @param $handle
 * @return null
 */
function qa_handle_to_userid( $handle )
/*
Return the userid corresponding to $handle (not case- or accent-sensitive)
 */
{
	if ( QA_FINAL_EXTERNAL_USERS ) {
		$handleuserids = qa_get_userids_from_public( array( $handle ) );
	} else {
		require_once QA_INCLUDE_DIR . 'king-db/users.php';
		$handleuserids = qa_db_user_get_handle_userids( array( $handle ) );
	}

	if ( count( $handleuserids ) == 1 ) {
		return reset( $handleuserids );
	}
	// don't use $handleuserids[$handle] since capitalization might be different

	return;
}

/**
 * @param $categoryids
 * @return mixed
 */
function qa_user_level_for_categories( $categoryids )
/*
Return the level of the logged in user for a post with $categoryids (expressing the full hierarchy to the final category)
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	require_once QA_INCLUDE_DIR . 'king-app/updates.php';

	$level = qa_get_logged_in_level();

	if ( count( $categoryids ) ) {
		$userlevels = qa_get_logged_in_levels();

		$categorylevels = array(); // create a map

		foreach ( $userlevels as $userlevel ) {
			if ( QA_ENTITY_CATEGORY == $userlevel['entitytype'] ) {
				$categorylevels[$userlevel['entityid']] = $userlevel['level'];
			}
		}

		foreach ( $categoryids as $categoryid ) {
			$level = max( $level, @$categorylevels[$categoryid] );
		}
	}

	return $level;
}

/**
 * @param $post
 * @return null
 */
function qa_user_level_for_post( $post )
/*
Return the level of the logged in user for $post, as retrieved from the database
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	if ( strlen( $post['categoryids'] ?? '' ) ) {
		return qa_user_level_for_categories( explode( ',', $post['categoryids'] ) );
	}

	return;
}

/**
 * @return mixed
 */
function qa_user_level_maximum()
/*
Return the maximum possible level of the logged in user in any context (i.e. for any category)
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	$level = qa_get_logged_in_level();

	$userlevels = qa_get_logged_in_levels();

	foreach ( $userlevels as $userlevel ) {
		$level = max( $level, $userlevel['level'] );
	}

	return $level;
}

/**
 * @param $permitoption
 * @param $post
 * @param $limitaction
 * @param null $checkblocks
 */
function qa_user_post_permit_error( $permitoption, $post, $limitaction = null, $checkblocks = true )
/*
Check whether the logged in user has permission to perform $permitoption on post $post (from the database)
Other parameters and the return value are as for qa_user_permit_error(...)
 */
{
	return qa_user_permit_error( $permitoption, $limitaction, qa_user_level_for_post( $post ), $checkblocks );
}

/**
 * @param $permitoption
 * @param $limitaction
 * @param null $checkblocks
 */
function qa_user_maximum_permit_error( $permitoption, $limitaction = null, $checkblocks = true )
/*
Check whether the logged in user would have permittion to perform $permitoption in any context (i.e. for any category)
Other parameters and the return value are as for qa_user_permit_error(...)
 */
{
	return qa_user_permit_error( $permitoption, $limitaction, qa_user_level_maximum(), $checkblocks );
}

/**
 * @param $permitoption
 * @param null $limitaction
 * @param null $userlevel
 * @param null $checkblocks
 * @return mixed
 */
function qa_user_permit_error( $permitoption = null, $limitaction = null, $userlevel = null, $checkblocks = true )
/*
Check whether the logged in user has permission to perform $permitoption. If $permitoption is null, this simply
checks whether the user is blocked. Optionally provide an $limitaction (see top of king-app-limits.php) to also check
against user or IP rate limits. You can pass in a QA_USER_LEVEL_* constant in $userlevel to consider the user at a
different level to usual (e.g. if they are performing this action in a category for which they have elevated
privileges). To ignore the user's blocked status, set $checkblocks to false.

Possible results, in order of priority (i.e. if more than one reason, the first will be given):
'level' => a special privilege level (e.g. expert) or minimum number of points is required
'login' => the user should login or register
'userblock' => the user has been blocked
'ipblock' => the ip address has been blocked
'confirm' => the user should confirm their email address
'approve' => the user needs to be approved by the site admins
'limit' => the user or IP address has reached a rate limit (if $limitaction specified)
false => the operation can go ahead
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	require_once QA_INCLUDE_DIR . 'king-app/limits.php';

	$userid = qa_get_logged_in_userid();

	if ( ! isset( $userlevel ) ) {
		$userlevel = qa_get_logged_in_level();
	}

	$flags = qa_get_logged_in_flags();

	if ( ! $checkblocks ) {
		$flags &=~QA_USER_FLAGS_USER_BLOCKED;
	}

	$error = qa_permit_error( $permitoption, $userid, $userlevel, $flags );

	if ( $checkblocks && ( ! $error ) && qa_is_ip_blocked() ) {
		$error = 'ipblock';
	}

	if (  ( ! $error ) && isset( $userid ) && ( $flags & QA_USER_FLAGS_MUST_CONFIRM ) && qa_opt( 'confirm_user_emails' ) ) {
		$error = 'confirm';
	}

	if (  ( ! $error ) && isset( $userid ) && ( $flags & QA_USER_FLAGS_MUST_APPROVE ) && qa_opt( 'moderate_users' ) ) {
		$error = 'approve';
	}

	if ( isset( $limitaction ) && ! $error ) {
		if ( qa_user_limits_remaining( $limitaction ) <= 0 ) {
			$error = 'limit';
		}
	}

	return $error;
}

/**
 * @param $permitoption
 * @param $userid
 * @param $userlevel
 * @param $userflags
 * @param $userpoints
 */
function qa_permit_error( $permitoption, $userid, $userlevel, $userflags, $userpoints = null )
/*
Check whether $userid (null for no user) can perform $permitoption. Result as for qa_user_permit_error(...).
If appropriate, pass the user's level in $userlevel, flags in $userflags and points in $userpoints.
If $userid is currently logged in, you can set $userpoints=null to retrieve them only if necessary.
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	$permit = isset( $permitoption ) ? qa_opt( $permitoption ) : QA_PERMIT_ALL;

	if ( isset( $userid ) && (  ( QA_PERMIT_POINTS == $permit ) || ( QA_PERMIT_POINTS_CONFIRMED == $permit ) || ( QA_PERMIT_APPROVED_POINTS == $permit ) ) ) {
		// deal with points threshold by converting as appropriate

		if (  ( ! isset( $userpoints ) ) && ( qa_get_logged_in_userid() == $userid ) ) {
			$userpoints = qa_get_logged_in_points();
		}
		// allow late retrieval of points (to avoid unnecessary DB query when using external users)

		if ( $userpoints >= qa_opt( $permitoption . '_points' ) ) {
			$permit = ( QA_PERMIT_APPROVED_POINTS == $permit ) ? QA_PERMIT_APPROVED :
			(  ( QA_PERMIT_POINTS_CONFIRMED == $permit ) ? QA_PERMIT_CONFIRMED : QA_PERMIT_USERS );
		}
		// convert if user has enough points
		else {
			$permit = QA_PERMIT_EXPERTS;
		}
		// otherwise show a generic message so they're not tempted to collect points just for this
	}
	if (  isset( $userid ) && ( QA_PERMIT_VERIFY == $permit ) ) {
		$permit = QA_PERMIT_VERIFY;
	}
	$pplan = false;
	if (  isset( $userid ) && ( QA_PERMIT_MEMBERSHIP == $permit ) ) {

		$type = qa_opt( $permitoption . '_plan' );
		$checkid = qa_db_read_one_assoc(qa_db_query_sub('SELECT id FROM ^membership WHERE userid=$ AND type=$ AND outdate > NOW()', $userid, $type), true);
		if ( qa_opt( $permitoption . '_plan' ) == 0) {
			$pplan = false;
		} elseif ( ! $checkid ) {
			$pplan = true;
		} else {
			$pplan = false;
		}
		$permit = QA_PERMIT_MEMBERSHIP;
		
	}

	return qa_permit_value_error( $permit, $userid, $userlevel, $userflags, $pplan );
}

/**
 * @param $permit
 * @param $userid
 * @param $userlevel
 * @param $userflags
 * @return mixed
 */
function qa_permit_value_error( $permit, $userid, $userlevel, $userflags, $pplan = null )
/*
Check whether $userid of level $userlevel with $userflags can reach the permission level in $permit
(generally retrieved from an option, but not always). Result as for qa_user_permit_error(...).
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	if ( $permit >= QA_PERMIT_ALL ) {
		$error = false;
	} elseif ( $permit >= QA_PERMIT_USERS ) {
		$error = isset( $userid ) ? false : 'login';
	} elseif ( $permit >= QA_PERMIT_CONFIRMED ) {
		if ( ! isset( $userid ) ) {
			$error = 'login';
		} elseif (
			QA_FINAL_EXTERNAL_USERS || // not currently supported by single sign-on integration
			( $userlevel >= QA_PERMIT_APPROVED ) || // if user approved or assigned to a higher level, no need
			( $userflags & QA_USER_FLAGS_EMAIL_CONFIRMED ) || // actual confirmation
			( ! qa_opt( 'confirm_user_emails' ) ) // if this option off, we can't ask it of the user
		) {
			$error = false;
		} else {
			$error = 'confirm';
		}
	} elseif ( $permit >= QA_PERMIT_VERIFY ) {
		if ( ! isset( $userid ) ) {
			$error = 'login';
		} elseif ( qa_get_logged_in_user_field('verified') ) {
			$error = false;
		} else {
			$error = 'verify';
		}
	} elseif ( $permit >= QA_PERMIT_MEMBERSHIP && qa_opt('enable_membership') ) {
		if ( ! isset( $userid ) ) {
			$error = 'login';
		} elseif ( ( chekmembership( $userid ) && ! $pplan ) || $userlevel >= QA_USER_LEVEL_MODERATOR ) {
			$error = false;
		} else {
			$error = 'membership';
		}
	} elseif ( $permit >= QA_PERMIT_APPROVED ) {
		if ( ! isset( $userid ) ) {
			$error = 'login';
		} elseif (
			( $userlevel >= QA_USER_LEVEL_APPROVED ) || // user has been approved
			( ! qa_opt( 'moderate_users' ) ) // if this option off, we can't ask it of the user
		) {
			$error = false;
		} else {
			$error = 'approve';
		}
	} elseif ( $permit >= QA_PERMIT_EXPERTS ) {
		$error = ( isset( $userid ) && ( $userlevel >= QA_USER_LEVEL_EXPERT ) ) ? false : 'level';
	} elseif ( $permit >= QA_PERMIT_EDITORS ) {
		$error = ( isset( $userid ) && ( $userlevel >= QA_USER_LEVEL_EDITOR ) ) ? false : 'level';
	} elseif ( $permit >= QA_PERMIT_MODERATORS ) {
		$error = ( isset( $userid ) && ( $userlevel >= QA_USER_LEVEL_MODERATOR ) ) ? false : 'level';
	} elseif ( $permit >= QA_PERMIT_ADMINS ) {
		$error = ( isset( $userid ) && ( $userlevel >= QA_USER_LEVEL_ADMIN ) ) ? false : 'level';
	} else {
		$error = ( isset( $userid ) && ( $userlevel >= QA_USER_LEVEL_SUPER ) ) ? false : 'level';
	}

	if ( isset( $userid ) && ( $userflags & QA_USER_FLAGS_USER_BLOCKED ) && ( 'level' != $error ) ) {
		$error = 'userblock';
	}

	return $error;
}

/**
 * @param $userlevel
 * @return mixed
 */
function qa_user_captcha_reason( $userlevel = null )
/*
Return whether a captcha is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
in a category for which they have elevated privileges).

Possible results:
'login' => captcha required because the user is not logged in
'approve' => captcha required because the user has not been approved
'confirm' => captcha required because the user has not confirmed their email address
false => captcha is not required
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	$reason = false;

	if ( ! isset( $userlevel ) ) {
		$userlevel = qa_get_logged_in_level();
	}

	if ( $userlevel < QA_USER_LEVEL_APPROVED ) {
		// approved users and above aren't shown captchas
		$userid = qa_get_logged_in_userid();

		if ( qa_opt( 'captcha_on_anon_post' ) && ! isset( $userid ) ) {
			$reason = 'login';
		} elseif ( qa_opt( 'moderate_users' ) && qa_opt( 'captcha_on_unapproved' ) ) {
			$reason = 'approve';
		} elseif ( qa_opt( 'confirm_user_emails' ) && qa_opt( 'captcha_on_unconfirmed' ) && ! ( qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED ) ) {
			$reason = 'confirm';
		}
	}

	return $reason;
}

/**
 * @param $userlevel
 */
function qa_user_use_captcha( $userlevel = null )
/*
Return whether a captcha should be presented to the logged in user for writing posts. You can pass in a
QA_USER_LEVEL_* constant in $userlevel to consider the user at a different level to usual.
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	return qa_user_captcha_reason( $userlevel ) != false;
}

/**
 * @param $userlevel
 * @return mixed
 */
function qa_user_moderation_reason( $userlevel = null )
/*
Return whether moderation is required for posts submitted by the current user. You can pass in a QA_USER_LEVEL_*
constant in $userlevel to consider the user at a different level to usual (e.g. if they are performing this action
in a category for which they have elevated privileges).

Possible results:
'login' => moderation required because the user is not logged in
'approve' => moderation required because the user has not been approved
'confirm' => moderation required because the user has not confirmed their email address
'points' => moderation required because the user has insufficient points
false => moderation is not required
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	$reason = false;

	if ( ! isset( $userlevel ) ) {
		$userlevel = qa_get_logged_in_level();
	}

	if (
		( $userlevel < QA_USER_LEVEL_EXPERT ) && // experts and above aren't moderated
		qa_user_permit_error( 'permit_moderate' ) // if the user can approve posts, no point in moderating theirs
	) {
		$userid = qa_get_logged_in_userid();

		if ( isset( $userid ) ) {
			if ( qa_opt( 'moderate_users' ) && qa_opt( 'moderate_unapproved' ) && ( $userlevel < QA_USER_LEVEL_APPROVED ) ) {
				$reason = 'approve';
			} elseif ( qa_opt( 'confirm_user_emails' ) && qa_opt( 'moderate_unconfirmed' ) && ! ( qa_get_logged_in_flags() & QA_USER_FLAGS_EMAIL_CONFIRMED ) ) {
				$reason = 'confirm';
			} elseif ( qa_opt( 'moderate_by_points' ) && ( qa_get_logged_in_points() < qa_opt( 'moderate_points_limit' ) ) ) {
				$reason = 'points';
			}
		} elseif ( qa_opt( 'moderate_anon_post' ) ) {
			$reason = 'login';
		}
	}

	return $reason;
}

/**
 * @param $userfield
 * @return mixed
 */
function qa_user_userfield_label( $userfield )
/*
Return the label to display for $userfield as retrieved from the database, using default if no name set
 */
{
	if ( isset( $userfield['content'] ) ) {
		return $userfield['content'];
	} else {
		$defaultlabels = array(
			'name'        => 'users/full_name',
			'about'       => 'users/about',
			'location'    => 'users/location',
			'website'     => 'users/website',
			'facebook-f'  => 'users/facebook-f',
			'twitter'     => 'users/twitter',
			'instagram'   => 'users/instagram',
			'reddit'      => 'users/reddit',
			'pinterest-p' => 'users/pinterest-p',
			'linkedin-in'  => 'users/linkedin-in',
		);

		if ( isset( $userfield['title'] ) ) {
			if ( isset( $defaultlabels[$userfield['title']] ) ) {
				return qa_lang( $defaultlabels[$userfield['title']] );
			}
		}
	}

	return '';
}

function qa_set_form_security_key()
/*
Set or extend the cookie in browser of non logged-in users which identifies them for the purposes of form security (anti-CSRF protection)
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	global $qa_form_key_cookie_set;

	if (  ( ! qa_is_logged_in() ) && ! @$qa_form_key_cookie_set ) {
		$qa_form_key_cookie_set = true;

		if ( strlen( $_COOKIE['qa_key'] ?? '' ) != QA_FORM_KEY_LENGTH ) {
			require_once QA_INCLUDE_DIR . 'king-util/string.php';
			$_COOKIE['qa_key'] = qa_random_alphanum( QA_FORM_KEY_LENGTH );
		}

		setcookie( 'qa_key', $_COOKIE['qa_key'], time() + 2 * QA_FORM_EXPIRY_SECS, '/', QA_COOKIE_DOMAIN ); // extend on every page request
	}
}

/**
 * @param $action
 * @param $timestamp
 */
function qa_calc_form_security_hash( $action, $timestamp )
/*
Return the form security (anti-CSRF protection) hash for an $action (any string), that can be performed within
QA_FORM_EXPIRY_SECS of $timestamp (in unix seconds) by the current user.
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	$salt = qa_opt( 'form_security_salt' );

	if ( qa_is_logged_in() ) {
		return sha1( $salt . '/' . $action . '/' . $timestamp . '/' . qa_get_logged_in_userid() . '/' . qa_get_logged_in_user_field( 'passsalt' ) );
	} else {
		return sha1( $salt . '/' . $action . '/' . $timestamp . '/' . @$_COOKIE['qa_key'] );
	}
	// lower security for non logged in users - code+cookie can be transferred
}

/**
 * @param $action
 */
function qa_get_form_security_code( $action )
/*
Return the full form security (anti-CSRF protection) code for an $action (any string) performed within
QA_FORM_EXPIRY_SECS of now by the current user.
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	qa_set_form_security_key();

	$timestamp = qa_opt( 'db_time' );

	return (int) qa_is_logged_in() . '-' . $timestamp . '-' . qa_calc_form_security_hash( $action, $timestamp );
}

/**
 * @param $action
 * @param $value
 */
function qa_check_form_security_code( $action, $value )
/*
Return whether $value matches the expected form security (anti-CSRF protection) code for $action (any string) and
that the code has not expired (if more than QA_FORM_EXPIRY_SECS have passed). Logs causes for suspicion.
 */
{
	if ( qa_to_override( __FUNCTION__ ) ) {
		$args = func_get_args();

		return qa_call_override( __FUNCTION__, $args );}

	$reportproblems = array();
	$silentproblems = array();

	if ( ! isset( $value ) ) {
		$silentproblems[] = 'code missing';
	} elseif ( ! strlen( (string)$value ) ) {
		$silentproblems[] = 'code empty';
	} else {
		$parts = explode( '-', $value );

		if ( count( $parts ) == 3 ) {
			$loggedin  = $parts[0];
			$timestamp = $parts[1];
			$hash      = $parts[2];
			$timenow   = qa_opt( 'db_time' );

			if ( $timestamp > $timenow ) {
				$reportproblems[] = 'time ' . ( $timestamp - $timenow ) . 's in future';
			} elseif ( $timestamp < ( $timenow - QA_FORM_EXPIRY_SECS ) ) {
				$silentproblems[] = 'timeout after ' . ( $timenow - $timestamp ) . 's';
			}

			if ( qa_is_logged_in() ) {
				if ( ! $loggedin ) {
					$silentproblems[] = 'now logged in';
				}
			} else {
				if ( $loggedin ) {
					$silentproblems[] = 'now logged out';
				} else {
					$key = @$_COOKIE['qa_key'];

					if ( ! isset( $key ) ) {
						$silentproblems[] = 'key cookie missing';
					} elseif ( ! strlen( (string)$key ) ) {
						$silentproblems[] = 'key cookie empty';
					} elseif ( strlen( (string)$key ) != QA_FORM_KEY_LENGTH ) {
						$reportproblems[] = 'key cookie ' . $key . ' invalid';
					}
				}
			}

			if ( empty( $silentproblems ) && empty( $reportproblems ) ) {
				if ( strtolower( qa_calc_form_security_hash( $action, $timestamp ) ) != strtolower( $hash ) ) {
					$reportproblems[] = 'code mismatch';
				}
			}
		} else {
			$reportproblems[] = 'code ' . $value . ' malformed';
		}
	}

	if ( count( $reportproblems ) ) {
		@error_log(
			'PHP King-Media form security violation for ' . $action .
			' by ' . ( qa_is_logged_in() ? ( 'userid ' . qa_get_logged_in_userid() ) : 'anonymous' ) .
			' (' . implode( ', ', array_merge( $reportproblems, $silentproblems ) ) . ')' .
			' on ' . @$_SERVER['REQUEST_URI'] .
			' via ' . @$_SERVER['HTTP_REFERER']
		);
	}

	return ( empty( $silentproblems ) && empty( $reportproblems ) );
}

/*
Omit PHP closing tag to help avoid accidental output
 */
/**
 * @param $user
 * @param $coversize
 * @param $class
 * @param null $asize
 * @return mixed
 */
function get_user_html( $user, $coversize = '1200', $class = null, $asize = '90' ) {
	$html = '<div class="user-boxx ' . $class . '">';
	$html .= '<div class="user-box">';
	$html .= '<div class="user-box-pt">';

	if ( $user ) {
		$userid = $user['userid'];
		$html .= '<div class="user-box-cover">';
		$html .= '<div data-king-img-src="' . get_avatar( $user['coverblobid'], $coversize, true ) . '" class="king-box-bg"></div>';
		$html .= '<div class="user-box-up" >';
		if ( 'king-profile' === $class ) {
			require_once QA_INCLUDE_DIR . 'king-db/selects.php';
			$links = qa_db_single_select( qa_db_user_profile_selectspec($userid, true) );

			$requiredKeys = ['facebook-f','instagram','linkedin-in','pinterest-p','reddit','twitter'];
			if (isset($links)) {
				$html .= '<div class="user-box-links">';
				foreach ($links as $key => $link) {
					if ( in_array( $key, $requiredKeys ) ) {
						if ($link) {
							if ($key === 'twitter') {
								$key = 'x-twitter';
							}
							$html .= '<a href="' . qa_html( $link ) . '"  data-toggle="tooltip" data-placement="top" title="' . qa_lang_html( 'users/'.$key ) . '" target="_blank"><i class="fa-brands fa-' . $key . '"></i></a>';
						}
						
					}	
				}
				$html .= '</div>';
			}
		}
		$html .= '<a href="' . qa_path_html( 'user/' . $user['handle'] ) . '" class="user-box-alink' . (  ( $user['verified'] ) ? ' averified' : '' ) . '">';
		$html .= get_avatar( $user['avatarblobid'], $asize );
		$html .= '</a>';
		$html .= '</div>';
		$html .= '</div>';
		$html .= '<div class="user-box-in">';
		$html .= '<div class="user-box-name">';
		$html .= '<a href="' . qa_path_html( 'user/' . $user['handle'] ) . '"><h3>' . $user['handle'] . '</h3></a>';

		if ( qa_get_logged_in_level() >= QA_USER_LEVEL_ADMIN ) {
			$html .= '<button class="verify-button' . (  ( $user['verified'] ) ? ' verified' : ' nverified' ) . '" onclick="return makeVerify(this);" data-userid="' . qa_html( $userid ) . '" data-toggle="tooltip" data-placement="top" title="' . qa_lang_html( 'misc/verified' ) . '"><i class="fa fa-check-circle"></i></button>';
		} elseif ( $user['verified'] ) {
			$html .= '<div class="verify-button' . (  ( $user['verified'] ) ? ' verified' : '' ) . '" ><i class="fa fa-check-circle"></i></div>';
		}
		$html .= membership_badge($userid);
		$html .= '</div>';


			$html .= '<div class="user-box-tp" >';
			if ( isset( $user['points'] ) ) {
			$html .= '<span class="user-box-point"><strong>' . qa_html( number_format( $user['points'] ) ) . '</strong> ' . qa_lang_html( 'admin/points_title' ) . '</span>';
			

			if ( qa_get_points_to_titles() ) {
				$html .= '<span class="user-box-title">' . qa_get_points_title_html( @$user['points'], qa_get_points_to_titles() ) . '</span>';
			}
			}

			if (qa_opt('enable_credits') &&  ( qa_get_logged_in_userid() === $userid || qa_get_logged_in_level() >= QA_USER_LEVEL_ADMIN )  ) {
				require_once QA_INCLUDE_DIR . 'king-db/metas.php';
				$cre = qa_db_usermeta_get($userid, 'credit');
				$ucre = !empty( $cre ) ? $cre : 0;
				$html .= '<a class="user-box-credits" href="'. qa_path_html( 'membership' ) .'"><i class="fa-solid fa-coins"></i>  <strong>' . qa_html( $ucre ) . '</strong> ' . qa_lang_html( 'misc/credits' ) . '</a>';


			}
			$html .= '</div>';


		$html .= user_numbers( $userid );
		$html .= '<div class="user-box-buttons">';
		$html .= '<div id="follow_' . $userid . '">';
		$html .= favorite_button( $userid );
		$html .= '</div>';

		if ( qa_opt( 'allow_private_messages' ) && qa_get_logged_in_userid() && ( qa_get_logged_in_userid() != $userid ) && ! ( isset( $user['flags'] ) & QA_USER_FLAGS_NO_MESSAGES ) ) {
			$html .= '<a class="king-message" href="' . qa_path_html( 'message/' . $user['handle'] ) . '" data-toggle="tooltip" data-placement="top" title="' . qa_lang_html( 'misc/send_message' ) . '"><i class="fas fa-envelope"></i></a>';
		}

		$html .= '</div>';
		$html .= '</div>';
	} else {
		$html .= '<div class="user-box-avatar" ></div>';
		$html .= '<div class="user-box-name noname"><h3>' . qa_lang_html( 'main/anonymous' ) . '</h3></div>';
	}

	$html .= '</div>';
	$html .= '</div>';
	$html .= '</div>';

	return $html;
}
/**
 * @param $userid
 * @param $type
 * @return mixed
 */
function king_ai_posts( $userid, $type ) {
    // Get the selectspec
    $selectspec = king_aiposts($userid, $type);

    // Fetch posts from the database
    $aiposts = qa_db_select_with_pending($selectspec);

    $html = '<div class="king-aiposts">';

    foreach ( $aiposts as $aipost ) {
        $html  .= '<div class="ai-result p' . qa_html( $aipost['postid'] ) . '" id="post' . qa_html( $aipost['postid'] ) . '">';
		$html .= '<div class="king-aipost-left">';
		$postid = $aipost['postid'];

		$model = qa_db_postmeta_get( $postid, 'model' );
		$extraz  = qa_db_postmeta_get( $postid, 'qa_q_extra' );
		$extras = @unserialize( $extraz );

			if ( $type === 'aimg' && is_array( $extras ) ) {
				foreach ( $extras as $extra ) {
					$text2 = king_get_uploads( $extra );
					$html .= '<a href="' . $text2['furl'] . '">';
					$html .= '<img class="ai-img" src="' . $text2['furl'] . '" width="' . $text2['width'] . '" height="' . $text2['height'] . '"/>';
					$html .= '</a>';
				}
			} elseif ( $type === 'aivid' ) {
				$vid = king_get_uploads( $extraz );
				$html .= '<video class="king-aivid" id="video_' . qa_html($postid) . '" autoplay loop muted playsinline>';
				$html .= '<source src="' . qa_html($vid['furl']) . '" type="video/mp4"></source>';
				$html .= '</video>';
				$html .= '<div class="king-aividcont">';
				if ( $model === 'veo3' || $model === 'veo3f') {
					$html .= '<button type="button" class="king-bbutton" onclick="toggleMute(\'video_' . qa_html($postid) . '\', this)"><i class="fa-solid fa-volume-xmark"></i></button>';

				}
				$html .= '<button type="button" class="king-bbutton" onclick="togglePlayStop(\'video_' . qa_html($postid) . '\', this)"><i class="fa-solid fa-pause"></i></button>';
				$html .= '</div>';
			}

		$html .= '</div>';
        $html .= '<div class="king-aipost-content">';
		if ( isset( $aipost['pcontent'])) {
			$asize = qa_db_postmeta_get( $postid, 'asize' );
			$stle = qa_db_postmeta_get( $postid, 'stle' );
			$imageid = qa_db_postmeta_get( $postid, 'pimage' );
			$html .= '<div class="king-aipost-text">' . qa_html( $aipost['pcontent'] ) . '</div>';
			$html .= '<div class="king-aipost-tags">';
			if ( isset( $model ) && $model ) {
				$html .= '<span class="ailabel">' . qa_lang('misc/'.$model) . '</span>';
			}
			if ( isset( $asize ) && $asize ) {
				$html .= '<span class="ailabel">' . qa_html( $asize ) . '</span>';
			}
			if ( isset( $stle ) && $stle ) {
				$html .= '<span class="ailabel">' . qa_html( $stle ) . '</span>';
			}
			$html .= '</div>';
			if ( isset( $imageid ) && $imageid ) {
				$imageurl = king_get_uploads($imageid);
				$html .= '<img class="ai-img" src="' . qa_html( $imageurl['furl'] ) . '" />';
			}
			$html .= '<button class="aipublish" onclick="aipublish(this)" data-id="' . qa_html( $aipost['postid'] ) . '">Publish</button>';
			$html .= '<button class="king-sbutton" onclick="deleteaipost(\'' . qa_html( $postid ) . '\', this)" data-toggle="tooltip" data-placement="top" title="' . qa_lang_html( 'misc/delete' ) . '"><i class="fa-solid fa-trash"></i></button>';

		}
        $html .= '</div>';
        $html .= '</div>';
    }
    $html .= '</div>';

    return $html;
}

/**
 * @param $userid
 * @return mixed
 */
function favorite_button( $userid ) {
	$loginuserid = qa_get_logged_in_userid();
	$html        = '';

	if ( isset( $loginuserid ) && $loginuserid != $userid && ! QA_FINAL_EXTERNAL_USERS ) {
		$favoritemap = qa_get_favorite_non_qs_map();
		$favorite    = @$favoritemap['user'][$userid];
		$html        = '<form method="post" action="">
				<button name="' . qa_html( 'favorite_' . QA_ENTITY_USER . '_' . $userid . '_' . (int) ! $favorite ) . '" onclick="return qa_favorite_click2(this);" type="submit" class="king-follow ' . ( $favorite ? 'active' : '' ) . '"><i class="fas fa-plus"></i>' . ( $favorite ? qa_lang_html( 'main/nav_unfollow' ) : qa_lang_html( 'main/nav_follow' ) ) . '</button>
				<input type="hidden" name="code" value="' . qa_get_form_security_code( 'favorite-' . QA_ENTITY_USER . '-' . $userid ) . '">
			</form>';
	}

	return $html;
}

/**
 * @param $userid
 * @return mixed
 */
function user_numbers( $userid ) {
	$getfcount = qa_db_read_one_value( qa_db_query_sub( "
			SELECT COUNT(*) FROM ^userfavorites
			WHERE userid = #
			and entitytype = $
			", $userid, 'U' ) );
	$getcount = qa_db_read_one_value( qa_db_query_sub( "
			SELECT COUNT(*) FROM ^userfavorites
			WHERE entityid = #
			and entitytype = $
			", $userid, 'U' ) );
	$getqcount = qa_db_read_one_value( qa_db_query_sub( "
			SELECT COUNT(*) FROM ^posts
			WHERE userid = #
			and type = $
			", $userid, 'Q' ) );
	$html = '<div class="king-stats">';
	$html .= '<span><strong>' . $getqcount . '</strong>' . qa_lang_html( 'misc/king_number_posts' ) . '</span>';
	$html .= '<span><strong>' . $getfcount . '</strong>' . qa_lang_html( 'misc/king_number_following' ) . '</span>';
	$html .= '<span><strong>' . $getcount . '</strong>' . qa_lang_html( 'misc/king_number_followers' ) . '</span>';
	$html .= '</div>';

	return $html;
}
